// =====================================================================================
//
//       Filename:  console.h
//
//    Description:  Implémentation de l'interface texte du logiciel
//
//        Version:  1.0
//        Created:  25/08/2009 08:32:00
//       Revision:  none
//       Compiler:  g++
//
//         Author:  François Hissel (fh), francois.hissel@m4x.org
//        Company:
//
// =====================================================================================

#include	<iostream>
#include	<fstream>
#include	<sstream>
#include	<string>
#include	<list>
#include	<deque>
#include	<vector>
#include	<cmath>
#include	<cstring>
#include	<cfloat>
#include	"gd.h"
#include	"gdfontt.h"
#include	"erreurs.h"
#include	"config.h"
#include	"parser.h"
#include	"ast.h"
#include	"raster.h"
#include	"geometrie.h"
#include	"graphe.h"
#include	"gisobjects.h"
#include	"routes.h"
#include	"application.h"

#ifdef  WITHGTK
#include	"window.h"
#endif

#include	"console.h"

using namespace std;

void Console::execute_commande(string com) {
	size_t pos=0;
	string word=getstring(com,pos,' ');
	if (string_compare(word,"open")) {
		if (app->reseau) delete app->reseau;
		app->reseau=new Reseau();
		string mif=getstring(com,pos,' ');
		string mid=getstring(com,pos,' ');
		app->reseau->importe_mid_mif(mif,mid,app->config);
		cout << "Réseau importé\n" << *(app->reseau);
	}
	else if (string_compare(word,"zoom")) {
		app->w_xmin=getfloat(com,pos,' ');
		app->w_ymin=getfloat(com,pos,' ');
		app->w_xmax=getfloat(com,pos,' ');
		app->w_ymax=getfloat(com,pos,' ');
		cout << "Nouvelles valeurs du zoom prises en compte\n";
		return;
	}
	else if (string_compare(word,"define")) {
		string param=getstring(com,pos,' ');
		if (string_compare(param,"field_way")) app->config.nom_sens=getstring(com,pos,' ');
		else if (string_compare(param,"value_direct")) app->config.sens_unique=getstring(com,pos,' ');
		else if (string_compare(param,"value_reverse")) app->config.sens_inverse=getstring(com,pos,' ');
		else if (string_compare(param,"value_both")) app->config.sens_double=getstring(com,pos,' ');
		else if (string_compare(param,"field_zbegin")) app->config.nom_zini=getstring(com,pos,' ');
		else if (string_compare(param,"field_zend")) app->config.nom_zfin=getstring(com,pos,' ');
		else if (string_compare(param,"weight_unit")){
			app->config.weight_unit=getstring(com,pos,' ');
		}
		else if (string_compare(param,"print_section")) {
			string value=getstring(com,pos,' ');
			size_t pos2=0;
			while (pos2<value.length()) {
				string name=getstring(value,pos2,',');
				app->affichage_troncon.push_back(name);
			}
		} else if (string_compare(param,"weight")) {
			app->config.poids=AST::get_E(com,pos,1);

		} else cout << "Variable inconnue : " << param << "\n";
	}
	else if (string_compare(word,"set")) {
		string param=getstring(com,pos,' ');
		if (string_compare(param,"raster_size")) app->raster_size=getint(com,pos,' ');
		else if (string_compare(param,"png_max_width")) app->max_width=getint(com,pos,' ');
		else if (string_compare(param,"png_max_height")) app->max_height=getint(com,pos,' ');
		else if (string_compare(param,"legend")) app->affiche_legende=(getint(com,pos,' ')==1);
		else if (string_compare(param,"color_coupe")) app->config.couleur_coupe=getstring(com,pos,' ');
		else if (string_compare(param,"background_transparent")) app->config.fond_transparent=(getint(com,pos,' ')==1);
		else cout << "Variable inconnue : " << param << "\n";
	}else if (string_compare(word,"export_osiris")) {
		app->config.fic_osiris_itin=getstring(com,pos,' ');
		app->config.fic_osiris_tourn=getstring(com,pos,' ');
	}else if (string_compare(word,"add_source")) {
		double xx=getfloat(com,pos,' ');
		double yy=getfloat(com,pos,' ');
		string param;
		try {param=getstring(com,pos,' ');} catch (ParseException) {param="";}
		app->ajoute_source(xx,yy,param);
		app->calcule_raster=true;
		cout << "Source ajoutée : " << app->sources.size() << " au total\n";
	}
	else if (string_compare(word,"clear_sources")) {
		app->vide_sources();
		app->calcule_raster=true;
		cout << "Sources supprimées.\n";
	}
	else if (string_compare(word,"add_measure")) {
		double xx=getfloat(com,pos,' ');
		double yy=getfloat(com,pos,' ');
		double zz=getfloat(com,pos,' ');
		app->echelles.push_back(Point(xx,yy));
		app->niveaux.push_back(zz);
		cout << "Echelle ajoutée : " << app->echelles.size() << " au total\n";
	}
	else if (string_compare(word,"clear_measures")) {
		app->echelles.clear();
		app->niveaux.clear();
	}
	else if (string_compare(word,"compute_weights")) {
		app->calcule_poids();
	}
	else if (string_compare(word,"draw_path")) {
		double x1=getfloat(com,pos,' ');
		double y1=getfloat(com,pos,' ');
		double x2=getfloat(com,pos,' ');
		double y2=getfloat(com,pos,' ');
		app->cherche_itineraire(x1,y1,x2,y2);
		cout << "OK, itinéraire calculé\n";
	}
	else if (string_compare(word,"compare_itineraire")) {
		double x1=getfloat(com,pos,' ');
		double y1=getfloat(com,pos,' ');
		double x2=getfloat(com,pos,' ');
		double y2=getfloat(com,pos,' ');
		app->compare_itineraire(x1,y1,x2,y2);
		cout << "OK, itinéraire comparé\n";
	}
	else if (string_compare(word,"clear_path")) {
		app->reseau->init_couleurs();
		cout << "OK, itinéraire supprimé\n";
	}
	else if (string_compare(word,"add_stake")) {
		double xx=getfloat(com,pos,' ');
		double yy=getfloat(com,pos,' ');
		string param;
		try {param=getstring(com,pos,' ');} catch (ParseException) {param="";}
		app->ajoute_enjeu(xx,yy,param);
		cout << "Enjeu ajouté : " << app->enjeux.size() << " au total\n";
	}
	else if (string_compare(word,"clear_stakes")) {
		app->vide_enjeux();
		cout << "Enjeux supprimés.\n";
	}
	else if (string_compare(word,"draw_tour")) {
		int mode;int nb_iter;int nb_fourmis;double alpha;double beta;double rho;
		try {mode=getint(com,pos,' ');} catch (ParseException) {mode=0;}
		try {nb_iter=getint(com,pos,' ');} catch (ParseException) {nb_iter=20;}
		try {nb_fourmis=getint(com,pos,' ');} catch (ParseException) {nb_fourmis=800;}
		try {alpha=getfloat(com,pos,' ');} catch (ParseException) {alpha=1;}
		try {beta=getfloat(com,pos,' ');} catch (ParseException) {beta=2;}
		try {rho=getfloat(com,pos,' ');} catch (ParseException) {rho=0.2;}
		app->calcule_tournee(mode,nb_iter,nb_fourmis,alpha,beta,rho);
	}
	else if (string_compare(word,"draw_accessibility")) {
		app->w_xmax = app->reseau->xmax; app->w_xmin = app->reseau->xmin;
		app->w_ymax = app->reseau->ymax; app->w_ymin = app->reseau->ymin;
		double width=app->w_xmax-app->w_xmin;
		double height=app->w_xmax-app->w_xmin;
		double vmax=0.0;
		try {
			vmax=getfloat(com,pos,' ');
		} catch (ParseException) {
			vmax=sqrt(width*width+height*height);	// Valeur par défaut si aucune valeur maximale n'est mentionnée
		}
		app->dessine_accessibilite(width/app->raster_size,vmax);
		cout << "Carte d'accessibilité calculée\n";
	}
	else if (string_compare(word,"draw_scheme")) {
		app->w_xmax = app->reseau->xmax; app->w_xmin = app->reseau->xmin;
		app->w_ymax = app->reseau->ymax; app->w_ymin = app->reseau->ymin;
		double width=app->w_xmax-app->w_xmin;
		app->dessine_desserte(width/app->raster_size);
		cout << "Carte de desserte calculée\n";
	}
	else if (string_compare(word,"draw_heights")) {
		double width=app->w_xmax-app->w_xmin;
		double vmax=0.0;
		try {
			vmax=getfloat(com,pos,' ');
			app->dessine_cote(width/app->raster_size,vmax);
		} catch (ParseException) {
			app->dessine_cote(width/app->raster_size,vmax);
			((RasterContinu*)(app->raster))->auto_bornes();
		}
		cout << "Carte des cotes d'eau calculée\n";
	}
	else if (string_compare(word,"export_png")) {
		string fich=getstring(com,pos,' ');
		enregistre(fich,app->affiche_legende);
	}
	else if (string_compare(word,"export_zoom")) {
		string fich=getstring(com,pos,' ');
		ofstream file;
		file.open(fich.c_str(), ios::out | ios::trunc);
		if(!file.is_open()){
			cout << "Erreur d'export du zoom" << endl;
			return;
		}
		file << std::fixed << app->reseau->xmin << " " << app->reseau->ymin << " " << app->reseau->xmax << " " << app->reseau->ymax;
		file.close();
		cout << "Zoom exporte\n";
	}
	else if (string_compare(word,"export_svg")) {
		app->config.svg_file=getstring(com,pos,' ');
		app->exporte_svg(app->config.svg_file);
		cout << "SVG du reseau routier genere\n";
	}
	else if (string_compare(word,"import_raster")) {
		string fich=getstring(com,pos,' ');
		app->importe_raster(fich);
		((RasterContinu*)(app->raster))->auto_bornes();
	}
	else if (string_compare(word,"export_raster")) {
		string fich=getstring(com,pos,' ');
		app->exporte_raster(fich);
	}
#ifdef  WITHGTK
	else if (string_compare(word,"open_window")) {
		openWindow(app);
	}
#endif
	else {
		cout << "Erreur de syntaxe ou commande inconnue\n";
	}
}

void Console::execute(istream &is) {
	while (!is.eof()) {
		string commande;
		getline(is,commande);
		//Ignore les lignes vides et les commentaires
		size_t comment=commande.find_first_of('#');
		if (comment!=string::npos) commande=commande.substr(0,comment);
		commande=trim(commande);
		//Exécute la commande
		try {
			if (commande.length()>0) execute_commande(commande);
		}
		catch (ParseException pe) {
			cout << "Erreur de syntaxe. " << pe.what() << "\n";
		}
		catch (exception e) {
			cout << "Erreur. " << e.what() << "\n";
		}
	}
}

void Console::enregistre(string fichier,bool legende) {
	const int LEGENDWIDTH=100;
	// Calcul des dimensions de l'image et du ratio d'affichage
	app->calcule_ratio(app->max_width,app->max_height,false);
	int width=(int)((app->wxmax-app->wxmin)*app->ratio);
	int height=(int)((app->wymax-app->wymin)*app->ratio);
	int legendwidth=legende?LEGENDWIDTH:0;
	// Création de l'image
	gdImagePtr im;
	FILE *fp;
	im = gdImageCreateTrueColor(width+legendwidth,height);
	if (!im) {
		cerr << "Impossible de créer l'image\n";
		return;
	}
	// Tracé de la carte raster
	int white = 0x00FFFFFF;
	// transparence si indiqué dans la config
	if(app->config.fond_transparent){
		gdImageColorTransparent(im, white);
		gdImageAlphaBlending(im, 0);
	}
	gdImageFilledRectangle(im,0,0,width-1+legendwidth,height-1,white);	// Remplissage de la carte avec un fond blanc
	if (app->raster) {
		double x=app->raster->xmin();
		double y;
		int cellsize=int(app->raster->cellule()*app->ratio)+1;
		for (int i=0;i<app->raster->taillex();i++) {
			y=app->raster->ymin();
			for (int j=0;j<app->raster->tailley();j++) {
				int xmin=app->get_x(x);
				int ymin=app->get_y(y);
				if (xmin>-cellsize && xmin<width && ymin>-cellsize && ymin<height) gdImageFilledRectangle(im,xmin,ymin,xmin+cellsize,ymin+cellsize,app->raster->couleur(i,j).to_int());
				y+=app->raster->cellule();
			}
			x+=app->raster->cellule();
		}
	}
	// Tracé de la carte vectorielle
	int color=0;
	for (vector<Arc*>::iterator itt=app->reseau->arcs.begin();itt!=app->reseau->arcs.end();itt++) {
		Troncon* pl=((Troncon*)(*itt));
		if (pl->num_points()>=2) {
			if (pl->tag!=0) {	// Si le tag est non nul, l'arc est sur un itinéraire à représenter. Dans ce cas, on change le style du tracé
				if(pl->tag == 5){ // itineraire de delestage a tracer en vert
					gdImageSetThickness(im,4);
					color=RGB::palette(2).to_int(); // vert
				}else if(pl->tag == 6){ // itineraire sans eau, par défaut en rouge
					gdImageSetThickness(im,4);
					color=RGB::palette(4).to_int(); // rouge
				}else{
					gdImageSetThickness(im,4);
					color=RGB::palette((pl->tag+3)%15).to_int();
				}
			} else if (pl->coupe) {	// Si le poids est trop important, l'itinéraire est coupé et on le représente dans une autre couleur
				gdImageSetThickness(im,1);
				stringstream s;
				s << hex << "0x00" << app->config.couleur_coupe;
				s >> color;
				//color=0x000000FF;
				//color=0x00FF0000;
			} else {
				gdImageSetThickness(im,1);
				color=0x00000000;
			}
			deque<Point*>::iterator itp=pl->begin();
			int cx=app->get_x((*itp)->x);
			int cy=app->get_y((*itp)->y);
			itp++;
			while (itp!=pl->end()) {
				int cxn=app->get_x((*itp)->x);
				int cyn=app->get_y((*itp)->y);
				gdImageLine(im,cx,cy,cxn,cyn,color);
				cx=cxn;cy=cyn;
				itp++;
			}
		}
	}
	//Tracé des points sources de la liste
	for (list<Ponctuel*>::iterator it=app->sources.begin();it!=app->sources.end();it++) {
		int xx=app->get_x((*it)->x);
		int yy=app->get_y((*it)->y);
		gdImageFilledEllipse(im,xx,yy,10,10,0x002020FF);
	}
	//Tracé des enjeux de la liste
	for (list<Ponctuel*>::iterator it=app->enjeux.begin();it!=app->enjeux.end();it++) {
		int xx=app->get_x((*it)->x);
		int yy=app->get_y((*it)->y);
		gdImageFilledEllipse(im,xx,yy,10,10,0x0020FF20);
	}
	// Tracé de la légende
	if (app->raster && legende) {
		gdImageFilledRectangle(im,width,0,width-1+LEGENDWIDTH,height-1,0x00FFFFFF);	// Effacement de l'emplacement de la légende
		gdImageRectangle(im,width+5,5,width+25,height-10,0x00000000);
		gdFontPtr font=gdFontGetTiny();
		if (app->raster->type()==Raster::CONTINU) {
			RasterContinu *rc=(RasterContinu*)(app->raster);
			for (int i=0;i<height-16;++i) gdImageLine(im,width+6,6+i,width+24,6+i,RGB::couleur((double)i/(height-15)).to_int());
			for (int i=0;i<5;++i) {
				ostringstream oss;
				oss << (rc->min+i*(rc->max-rc->min)/4);
				string w=oss.str();
				char word[w.length()+1];
				strcpy(word,w.c_str());
				gdImageString(im,font,width+26,5+(height-15)*i/4,(unsigned char*)word,0x00000000);
			}
		} else {
			RasterDiscret *rd=(RasterDiscret*)(app->raster);
			for (int i=0;i<rd->classes();++i) gdImageFilledRectangle(im,width+6,6+(height-17)*i/rd->classes(),width+24,6+(height-17)*(i+1)/rd->classes()-1,rd->couleur(i).to_int());
			list<Ponctuel*>::iterator it=app->sources.begin();
			for (int i=0;i<rd->classes();++i,it++) {
				string w=(*it)->get_string(0);
				if (w.size()==0) {
					ostringstream oss;
					oss << i;
					w=oss.str();
				}
				char word[w.length()+1];
				strcpy(word,w.c_str());
				gdImageString(im,font,width+26,(int)(6+(height-17)*(i+0.5)/rd->classes()),(unsigned char*)word,0x00000000);
			}
		}
	}
	// Enregistrement de l'image sur le fichier
	fp = fopen(fichier.c_str(),"wb");
	if (!fp) {
		cerr << "Impossible d'ouvrir le fichier " << fichier << " en écriture\n";
	}
	gdImagePng(im, fp);
	fclose(fp);
	gdImageDestroy(im);
	cout << "Image exportée : " << fichier << "\n";
}

void Console::affiche(const string &message,MessageType type,const string &titre) {
	cout << message << endl;
}

void Console::affiche_erreur(const string &message,MessageType type,const string &titre) {
	cerr << message << "\n";
}

/****************************************/
/*  IMPLEMENTATION DES AUTRES FONCTIONS */
/****************************************/
void openConsole(Application *papp,istream &iss) {
	Console c(papp);
	c.execute(iss);
}
